/*
    libparted - a library for manipulating disk partitions

    original version by Matt Domsch <Matt_Domsch@dell.com>
    Disclaimed into the Public Domain

    Portions Copyright (C) 2001, 2002, 2003, 2005, 2006
        Free Software Foundation, Inc.

    EFI GUID Partition Table handling
    Per Intel EFI Specification v1.02
    http://developer.intel.com/technology/efi/efi.htm

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/

#include "config.h"

#include <parted/parted.h>
#include <parted/debug.h>
#include <parted/endian.h>
#include <parted/crc32.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <uuid/uuid.h>

#if ENABLE_NLS
#  include <libintl.h>
#  define _(String) gettext (String)
#else
#  define _(String) (String)
#endif				/* ENABLE_NLS */

#define EFI_PMBR_OSTYPE_EFI 0xEE
#define MSDOS_MBR_SIGNATURE 0xaa55

#define GPT_HEADER_SIGNATURE 0x5452415020494645LL

/* NOTE: the document that describes revision 1.00 is labelled "version 1.02",
 * so some implementors got confused...
 */
#define GPT_HEADER_REVISION_V1_02 0x00010200
#define GPT_HEADER_REVISION_V1_00 0x00010000
#define GPT_HEADER_REVISION_V0_99 0x00009900

typedef uint16_t efi_char16_t;	/* UNICODE character */

typedef struct {
        uint32_t time_low;
        uint16_t time_mid;
        uint16_t time_hi_and_version;
        uint8_t  clock_seq_hi_and_reserved;
        uint8_t  clock_seq_low;
        uint8_t  node[6];
} /* __attribute__ ((packed)) */ efi_guid_t;
/* commented out "__attribute__ ((packed))" to work around gcc bug (fixed
 * in gcc3.1): __attribute__ ((packed)) breaks addressing on initialized
 * data.  It turns out we don't need it in this case, so it doesn't break
 * anything :)
 */

#define UNUSED_ENTRY_GUID    \
    ((efi_guid_t) { 0x00000000, 0x0000, 0x0000, 0x00, 0x00, \
    		    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }})
#define PARTITION_SYSTEM_GUID \
    ((efi_guid_t) { PED_CPU_TO_LE32 (0xC12A7328), PED_CPU_TO_LE16 (0xF81F), \
    		    PED_CPU_TO_LE16 (0x11d2), 0xBA, 0x4B, \
		    { 0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B }})
#define LEGACY_MBR_PARTITION_GUID \
    ((efi_guid_t) { PED_CPU_TO_LE32 (0x024DEE41), PED_CPU_TO_LE16 (0x33E7), \
		    PED_CPU_TO_LE16 (0x11d3, 0x9D, 0x69, \
		    { 0x00, 0x08, 0xC7, 0x81, 0xF3, 0x9F }})
#define PARTITION_MSFT_RESERVED_GUID \
    ((efi_guid_t) { PED_CPU_TO_LE32 (0xE3C9E316), PED_CPU_TO_LE16 (0x0B5C), \
    		    PED_CPU_TO_LE16 (0x4DB8), 0x81, 0x7D, \
		    { 0xF9, 0x2D, 0xF0, 0x02, 0x15, 0xAE }})
#define PARTITION_BASIC_DATA_GUID \
    ((efi_guid_t) { PED_CPU_TO_LE32 (0xEBD0A0A2), PED_CPU_TO_LE16 (0xB9E5), \
    		    PED_CPU_TO_LE16 (0x4433), 0x87, 0xC0, \
		    { 0x68, 0xB6, 0xB7, 0x26, 0x99, 0xC7 }})
#define PARTITION_RAID_GUID \
    ((efi_guid_t) { PED_CPU_TO_LE32 (0xa19d880f), PED_CPU_TO_LE16 (0x05fc), \
    		    PED_CPU_TO_LE16 (0x4d3b), 0xa0, 0x06, \
		    { 0x74, 0x3f, 0x0f, 0x84, 0x91, 0x1e }})
#define PARTITION_SWAP_GUID \
    ((efi_guid_t) { PED_CPU_TO_LE32 (0x0657fd6d), PED_CPU_TO_LE16 (0xa4ab), \
    		    PED_CPU_TO_LE16 (0x43c4), 0x84, 0xe5, \
		    { 0x09, 0x33, 0xc8, 0x4b, 0x4f, 0x4f }})
#define PARTITION_LVM_GUID \
    ((efi_guid_t) { PED_CPU_TO_LE32 (0xe6d6d379), PED_CPU_TO_LE16 (0xf507), \
    		    PED_CPU_TO_LE16 (0x44c2), 0xa2, 0x3c, \
		    { 0x23, 0x8f, 0x2a, 0x3d, 0xf9, 0x28 }})
#define PARTITION_RESERVED_GUID \
    ((efi_guid_t) { PED_CPU_TO_LE32 (0x8da63339), PED_CPU_TO_LE16 (0x0007), \
    		    PED_CPU_TO_LE16 (0x60c0), 0xc4, 0x36, \
		    { 0x08, 0x3a, 0xc8, 0x23, 0x09, 0x08 }})
#define PARTITION_HPSERVICE_GUID \
    ((efi_guid_t) { PED_CPU_TO_LE32 (0xe2a1e728), PED_CPU_TO_LE16 (0x32e3), \
		    PED_CPU_TO_LE16 (0x11d6), 0xa6, 0x82, \
		    { 0x7b, 0x03, 0xa0, 0x00, 0x00, 0x00 }})
#define PARTITION_APPLE_HFS_GUID \
    ((efi_guid_t) { PED_CPU_TO_LE32 (0x48465300), PED_CPU_TO_LE16 (0x0000), \
		    PED_CPU_TO_LE16 (0x11AA), 0xaa, 0x11, \
		    { 0x00, 0x30, 0x65, 0x43, 0xEC, 0xAC }})

typedef struct _GuidPartitionTableHeader_t {
	uint64_t Signature;
	uint32_t Revision;
	uint32_t HeaderSize;
	uint32_t HeaderCRC32;
	uint32_t Reserved1;
	uint64_t MyLBA;
	uint64_t AlternateLBA;
	uint64_t FirstUsableLBA;
	uint64_t LastUsableLBA;
	efi_guid_t DiskGUID;
	uint64_t PartitionEntryLBA;
	uint32_t NumberOfPartitionEntries;
	uint32_t SizeOfPartitionEntry;
	uint32_t PartitionEntryArrayCRC32;
	uint8_t* Reserved2;
} __attribute__ ((packed)) GuidPartitionTableHeader_t;

typedef struct _GuidPartitionEntryAttributes_t {
#ifdef __GNUC__ /* XXX narrow this down to !TinyCC */
	uint64_t RequiredToFunction:1;
	uint64_t Reserved:47;
        uint64_t GuidSpecific:16;
#else
#       warning "Using crippled partition entry type"
	uint32_t RequiredToFunction:1;
	uint32_t Reserved:32;
	uint32_t LOST:5;
        uint32_t GuidSpecific:16;
#endif
} __attribute__ ((packed)) GuidPartitionEntryAttributes_t;

typedef struct _GuidPartitionEntry_t {
	efi_guid_t PartitionTypeGuid;
	efi_guid_t UniquePartitionGuid;
	uint64_t StartingLBA;
	uint64_t EndingLBA;
	GuidPartitionEntryAttributes_t Attributes;
	efi_char16_t PartitionName[72 / sizeof(efi_char16_t)];
} __attribute__ ((packed)) GuidPartitionEntry_t;

#define GPT_PMBR_LBA 0
#define GPT_PMBR_SECTORS 1
#define GPT_PRIMARY_HEADER_LBA 1
#define GPT_HEADER_SECTORS 1
#define GPT_PRIMARY_PART_TABLE_LBA 2 

/* 
   These values are only defaults.  The actual on-disk structures
   may define different sizes, so use those unless creating a new GPT disk!
*/

#define GPT_DEFAULT_PARTITION_ENTRY_ARRAY_SIZE 16384

/* Number of actual partition entries should be calculated as: */
#define GPT_DEFAULT_PARTITION_ENTRIES \
        (GPT_DEFAULT_PARTITION_ENTRY_ARRAY_SIZE / \
         sizeof(GuidPartitionEntry_t))


typedef struct _PartitionRecord_t {
	uint8_t BootIndicator;	/* Not used by EFI firmware. Set to 0x80 to indicate that this
				   is the bootable legacy partition. */
	uint8_t StartHead;	/* Start of partition in CHS address, not used by EFI firmware. */
	uint8_t StartSector;	/* Start of partition in CHS address, not used by EFI firmware. */
	uint8_t StartTrack;	/* Start of partition in CHS address, not used by EFI firmware. */
	uint8_t OSType;		/* OS type. A value of 0xEF defines an EFI system partition.
				   Other values are reserved for legacy operating systems, and
				   allocated independently of the EFI specification. */
	uint8_t EndHead;	/* End of partition in CHS address, not used by EFI firmware. */
	uint8_t EndSector;	/* End of partition in CHS address, not used by EFI firmware. */
	uint8_t EndTrack;	/* End of partition in CHS address, not used by EFI firmware. */
	uint32_t StartingLBA;	/* Starting LBA address of the partition on the disk. Used by
				   EFI firmware to define the start of the partition. */
	uint32_t SizeInLBA;	/* Size of partition in LBA. Used by EFI firmware to determine
				   the size of the partition. */
} __attribute__ ((packed)) PartitionRecord_t;

/* Protected Master Boot Record  & Legacy MBR share same structure */
/* Needs to be packed because the u16s force misalignment. */
typedef struct _LegacyMBR_t {
	uint8_t BootCode[440];
	uint32_t UniqueMBRSignature;
	uint16_t Unknown;
	PartitionRecord_t PartitionRecord[4];
	uint16_t Signature;
} __attribute__ ((packed)) LegacyMBR_t;

/* uses libparted's disk_specific field in PedDisk, to store our info */
typedef struct _GPTDiskData {
	PedGeometry	data_area;
	int		entry_count;
	efi_guid_t	uuid;
} GPTDiskData;

/* uses libparted's disk_specific field in PedPartition, to store our info */
typedef struct _GPTPartitionData {
	efi_guid_t	type;
	efi_guid_t	uuid;
	char		name[37];
	int		lvm;
	int		raid;
	int		boot;
	int		hp_service;
        int             hidden;
        int             msftres;
} GPTPartitionData;

static PedDiskType gpt_disk_type;


static inline uint32_t
pth_get_size (const PedDevice* dev)
{
        return GPT_HEADER_SECTORS * dev->sector_size;
}


static inline uint32_t
pth_get_size_static (const PedDevice* dev)
{
        return sizeof (GuidPartitionTableHeader_t) - sizeof (uint8_t*);
}


static inline uint32_t
pth_get_size_rsv2 (const PedDevice* dev)
{
        return pth_get_size(dev) - pth_get_size_static(dev);
}


static GuidPartitionTableHeader_t*
pth_new (const PedDevice* dev)
{
        GuidPartitionTableHeader_t* pth = ped_malloc (
                        sizeof (GuidPartitionTableHeader_t)
                        + sizeof (uint8_t));
        
        pth->Reserved2 = ped_malloc ( pth_get_size_rsv2 (dev) );

        return pth;
}


static GuidPartitionTableHeader_t*
pth_new_zeroed (const PedDevice* dev)
{
        GuidPartitionTableHeader_t* pth = pth_new (dev);
        
        memset (pth, 0, pth_get_size_static (dev));
        memset (pth->Reserved2, 0, pth_get_size_rsv2 (dev));        
        
        return (pth);
}
                        

static GuidPartitionTableHeader_t*
pth_new_from_raw (const PedDevice* dev, const uint8_t* pth_raw)
{
        GuidPartitionTableHeader_t* pth = pth_new (dev); 

        PED_ASSERT (pth_raw != NULL, return 0);
        
        memcpy (pth, pth_raw, pth_get_size_static (dev));
        memcpy (pth->Reserved2, pth_raw + pth_get_size_static (dev),
                        pth_get_size_rsv2 (dev));
        
        return pth;
}

static void
pth_free (GuidPartitionTableHeader_t* pth)
{
	PED_ASSERT (pth != NULL, return);
	PED_ASSERT (pth->Reserved2 != NULL, return);

	ped_free (pth->Reserved2);
	ped_free (pth);
}

static uint8_t*
pth_get_raw (const PedDevice* dev, const GuidPartitionTableHeader_t* pth)
{
        uint8_t* pth_raw = ped_malloc (pth_get_size (dev));
        int size_static = pth_get_size_static (dev);
        
        PED_ASSERT (pth != NULL, return 0);
        PED_ASSERT (pth->Reserved2 != NULL, return 0);
       
        memcpy (pth_raw, pth, size_static);
        memcpy (pth_raw + size_static, pth->Reserved2, pth_get_size_rsv2 (dev));
        
        return pth_raw;
}


/**
 * swap_uuid_and_efi_guid() - converts between uuid formats
 * @uuid - uuid_t in either format (converts it to the other)
 *
 * There are two different representations for Globally Unique Identifiers
 * (GUIDs or UUIDs).
 * 
 * The RFC specifies a UUID as a string of 16 bytes, essentially
 * a big-endian array of char.
 * Intel, in their EFI Specification, references the same RFC, but
 * then defines a GUID as a structure of little-endian fields.
 * Coincidentally, both structures have the same format when unparsed.
 *
 * When read from disk, EFI GUIDs are in struct of little endian format,
 * and need to be converted to be treated as uuid_t in memory.
 *
 * When writing to disk, uuid_ts need to be converted into EFI GUIDs.
 *
 * Blame Intel.
 */
static void
swap_uuid_and_efi_guid(uuid_t uuid)
{
	int i;
	efi_guid_t *guid = (efi_guid_t *)uuid;

	PED_ASSERT(uuid != NULL, return);
	guid->time_low            = PED_SWAP32(guid->time_low);
	guid->time_mid            = PED_SWAP16(guid->time_mid);
	guid->time_hi_and_version = PED_SWAP16(guid->time_hi_and_version);
}

/* returns the EFI-style CRC32 value for buf
 *	This function uses the crc32 function by Gary S. Brown,
 * but seeds the function with ~0, and xor's with ~0 at the end.
 */
static inline uint32_t
efi_crc32(const void *buf, unsigned long len)
{
	return (__efi_crc32(buf, len, ~0L) ^ ~0L);
}

static inline uint32_t
pth_crc32(const PedDevice* dev, const GuidPartitionTableHeader_t* pth)
{
        uint8_t* pth_raw = pth_get_raw (dev, pth);
        uint32_t crc32 = 0;
       
        PED_ASSERT (dev != NULL, return 0);
        PED_ASSERT (pth != NULL, return 0);
       
        crc32 = efi_crc32 (pth_raw, pth_get_size_static (dev));

        ped_free (pth_raw);
      
        return crc32;
}

static inline int
guid_cmp (efi_guid_t left, efi_guid_t right)
{
	return memcmp(&left, &right, sizeof(efi_guid_t));
}

/* checks if 'mbr' is a protective MBR partition table */
static inline int
_pmbr_is_valid (const LegacyMBR_t* mbr)
{
	int i;

	PED_ASSERT(mbr != NULL, return 0);

	if (mbr->Signature != PED_CPU_TO_LE16(MSDOS_MBR_SIGNATURE))
		return 0;
	for (i = 0; i < 4; i++) {
		if (mbr->PartitionRecord[i].OSType == EFI_PMBR_OSTYPE_EFI)
			return 1;
	}
	return 0;
}

static int
gpt_probe (const PedDevice * dev)
{
	GuidPartitionTableHeader_t* gpt = NULL;
        uint8_t* pth_raw = ped_malloc (pth_get_size (dev));
	LegacyMBR_t legacy_mbr;
	int gpt_sig_found = 0;

	PED_ASSERT (dev != NULL, return 0);

	if (ped_device_read(dev, pth_raw, 1, GPT_HEADER_SECTORS)
	|| ped_device_read(dev, pth_raw, dev->length - 1, GPT_HEADER_SECTORS)) {
		gpt = pth_new_from_raw (dev, pth_raw);
		if (gpt->Signature == PED_CPU_TO_LE64(GPT_HEADER_SIGNATURE))
			gpt_sig_found = 1;
	}
	
        ped_free (pth_raw);

        if (gpt)
	        pth_free (gpt);

        
	if (!gpt_sig_found)
		return 0;

	if (ped_device_read(dev, &legacy_mbr, 0, GPT_HEADER_SECTORS)) {
		if (!_pmbr_is_valid (&legacy_mbr)) {
			int ex_status = ped_exception_throw (
				PED_EXCEPTION_WARNING,
				PED_EXCEPTION_YES_NO,
			_("%s contains GPT signatures, indicating that it has "
			  "a GPT table.  However, it does not have a valid "
			  "fake msdos partition table, as it should.  Perhaps "
			  "it was corrupted -- possibly by a program that "
			  "doesn't understand GPT partition tables.  Or "
			  "perhaps you deleted the GPT table, and are now "
			  "using an msdos partition table.  Is this a GPT "
			  "partition table?"),
				dev->path);
			if (ex_status == PED_EXCEPTION_NO)
				return 0;
		}
	}

	return 1;
}

#ifndef DISCOVER_ONLY
/* writes zeros to the PMBR and the primary and alternate GPTHs and PTEs */
static int
gpt_clobber(PedDevice * dev)
{
	LegacyMBR_t pmbr;
        uint8_t* zeroed_pth_raw = ped_malloc (pth_get_size (dev));
        uint8_t* pth_raw = ped_malloc (pth_get_size (dev));
	GuidPartitionTableHeader_t* gpt;
	GuidPartitionEntry_t ptes[GPT_DEFAULT_PARTITION_ENTRIES];

	PED_ASSERT (dev != NULL, return 0);

	memset(&pmbr, 0, sizeof(pmbr));
	memset(zeroed_pth_raw, 0, pth_get_size (dev));
	
        /*
         * TO DISCUSS: check whether checksum is correct?
         * If not, we might get a wrong AlternateLBA field and destroy
         * one sector of random data.
         */
	if (!ped_device_read(dev, pth_raw,
			     GPT_PRIMARY_HEADER_LBA, GPT_HEADER_SECTORS))
                goto error_free;

        gpt = pth_new_from_raw (dev, pth_raw);
        
	if (!ped_device_write(dev, &pmbr, GPT_PMBR_LBA, GPT_PMBR_SECTORS))
                goto error_free_with_gpt;
	if (!ped_device_write(dev, &zeroed_pth_raw,
			      GPT_PRIMARY_HEADER_LBA, GPT_HEADER_SECTORS))
                goto error_free_with_gpt;
	if (!ped_device_write(dev, &zeroed_pth_raw, dev->length - GPT_HEADER_SECTORS,
			      GPT_HEADER_SECTORS))
                goto error_free_with_gpt;

	if ((PedSector) PED_LE64_TO_CPU (gpt->AlternateLBA) < dev->length - 1) {
		if (!ped_device_write(dev, gpt,
				      PED_LE64_TO_CPU (gpt->AlternateLBA),
				      GPT_HEADER_SECTORS))
			return 0;
	}
        
	pth_free (gpt);

        return 1;
        
error_free_with_gpt:
        pth_free (gpt);
error_free:
        ped_free (pth_raw);
        ped_free (zeroed_pth_raw);
	return 0;
}
#endif /* !DISCOVER_ONLY */

static PedDisk *
gpt_alloc (const PedDevice * dev)
{
	PedDisk* disk;
	GPTDiskData *gpt_disk_data;
	PedSector data_start, data_end;

	disk = _ped_disk_alloc ((PedDevice*)dev, &gpt_disk_type);
	if (!disk)
		goto error;
	disk->disk_specific = gpt_disk_data = ped_malloc (sizeof (GPTDiskData));
	if (!disk->disk_specific)
		goto error_free_disk;

	data_start = 2 + GPT_DEFAULT_PARTITION_ENTRY_ARRAY_SIZE / dev->sector_size;
	data_end = dev->length - 2
		   - GPT_DEFAULT_PARTITION_ENTRY_ARRAY_SIZE / dev->sector_size;
	ped_geometry_init (&gpt_disk_data->data_area, dev, data_start,
			   data_end - data_start + 1);
	gpt_disk_data->entry_count = GPT_DEFAULT_PARTITION_ENTRIES;
	uuid_generate ((unsigned char*) &gpt_disk_data->uuid);
	swap_uuid_and_efi_guid((unsigned char*)(&gpt_disk_data->uuid));
	return disk;

error_free_disk:
	ped_free (disk);
error:
	return NULL;
}

static PedDisk*
gpt_duplicate (const PedDisk* disk)
{
	PedDisk* new_disk;
	GPTDiskData* new_disk_data;
	GPTDiskData* old_disk_data;

	new_disk = ped_disk_new_fresh (disk->dev, &gpt_disk_type);
	if (!new_disk)
		return NULL;

	old_disk_data = disk->disk_specific;
	new_disk_data = new_disk->disk_specific;

	ped_geometry_init (&new_disk_data->data_area, disk->dev,
			   old_disk_data->data_area.start,
			   old_disk_data->data_area.length);
	new_disk_data->entry_count = old_disk_data->entry_count;
	new_disk_data->uuid = old_disk_data->uuid;
	return new_disk;
}

static void
gpt_free(PedDisk * disk)
{
	ped_disk_delete_all (disk);
	ped_free (disk->disk_specific);
	_ped_disk_free (disk);
}

static int
_header_is_valid (const PedDevice* dev, GuidPartitionTableHeader_t* gpt)
{
	uint32_t crc, origcrc;

	if (PED_LE64_TO_CPU (gpt->Signature) != GPT_HEADER_SIGNATURE)
		return 0;
	if (PED_LE32_TO_CPU (gpt->HeaderSize)
			> pth_get_size_static (dev))
		return 0;

	origcrc = gpt->HeaderCRC32;
	gpt->HeaderCRC32 = 0;
	crc = pth_crc32 (dev, gpt);
	gpt->HeaderCRC32 = origcrc;

	return crc == PED_LE32_TO_CPU (origcrc);
}

static int
_read_header (const PedDevice* dev, GuidPartitionTableHeader_t** gpt,
              PedSector where)
{
        uint8_t* pth_raw = ped_malloc (pth_get_size (dev));

	PED_ASSERT (dev != NULL, return 0);

	if (!ped_device_read (dev, pth_raw, where, GPT_HEADER_SECTORS)) {
                ped_free (pth_raw); 
		return 0;
        }
 
        *gpt = pth_new_from_raw (dev, pth_raw);
        
        ped_free (pth_raw);

        if (_header_is_valid (dev, *gpt))
                return 1;

        pth_free (*gpt);
        return 0;
}

static int
_parse_header (PedDisk* disk, GuidPartitionTableHeader_t* gpt)
{ 
	GPTDiskData* gpt_disk_data = disk->disk_specific;
	PedSector first_usable;
	PedSector last_usable;

	PED_ASSERT (_header_is_valid (disk->dev, gpt), return 0);

#ifndef DISCOVER_ONLY
	if (PED_CPU_TO_LE32 (gpt->Revision) > GPT_HEADER_REVISION_V1_02
	    || PED_CPU_TO_LE32 (gpt->HeaderSize) != pth_get_size_static (
                                                        disk->dev)) {
		if (ped_exception_throw (
			PED_EXCEPTION_WARNING,
			PED_EXCEPTION_IGNORE_CANCEL,
			_("The format of the GPT partition table is version "
			  "%x, which is newer than what Parted can "
			  "recognise.  Please tell us!  bug-parted@gnu.org"),
			PED_CPU_TO_LE32 (gpt->Revision))
				!= PED_EXCEPTION_IGNORE)
			return 0;
	}
#endif

	first_usable = PED_CPU_TO_LE64 (gpt->FirstUsableLBA);
	last_usable = PED_CPU_TO_LE64 (gpt->LastUsableLBA);
	PED_ASSERT (last_usable > first_usable, return 0);
	PED_ASSERT (last_usable <= disk->dev->length, return 0);
	ped_geometry_init (&gpt_disk_data->data_area, disk->dev,
			   first_usable, last_usable - first_usable + 1);

	gpt_disk_data->entry_count
		= PED_CPU_TO_LE32 (gpt->NumberOfPartitionEntries);
	PED_ASSERT (gpt_disk_data->entry_count > 0, return 0);
	PED_ASSERT (gpt_disk_data->entry_count <= 8192, return 0);

	gpt_disk_data->uuid = gpt->DiskGUID;

	return 1;
}

static PedPartition*
_parse_part_entry (PedDisk* disk, GuidPartitionEntry_t* pte)
{
	PedPartition* part;
	GPTPartitionData* gpt_part_data;
	unsigned int i;

	part = ped_partition_new (disk, 0, NULL,
			PED_LE64_TO_CPU(pte->StartingLBA),
			PED_LE64_TO_CPU(pte->EndingLBA));
	if (!part)
		return NULL;

	gpt_part_data = part->disk_specific;
	gpt_part_data->type = pte->PartitionTypeGuid;
	gpt_part_data->uuid = pte->UniquePartitionGuid;
	for (i = 0; i < 72 / sizeof (efi_char16_t); i++)
		gpt_part_data->name[i] = (efi_char16_t) PED_LE16_TO_CPU(
					   (uint16_t) pte->PartitionName[i]);
	gpt_part_data->name[i] = 0;
        
        gpt_part_data->lvm = gpt_part_data->raid 
                = gpt_part_data->boot = gpt_part_data->hp_service
                = gpt_part_data->hidden = gpt_part_data->msftres = 0;

        if (pte->Attributes.RequiredToFunction & 0x1)
                gpt_part_data->hidden = 1;
       
	if (!guid_cmp (gpt_part_data->type, PARTITION_SYSTEM_GUID))
		gpt_part_data->boot = 1;
	else if (!guid_cmp (gpt_part_data->type, PARTITION_RAID_GUID))
		gpt_part_data->raid = 1;
	else if (!guid_cmp (gpt_part_data->type, PARTITION_LVM_GUID))
		gpt_part_data->lvm = 1;
	else if (!guid_cmp (gpt_part_data->type, PARTITION_HPSERVICE_GUID))
		gpt_part_data->hp_service = 1;
        else if (!guid_cmp (gpt_part_data->type, PARTITION_MSFT_RESERVED_GUID))
                gpt_part_data->msftres = 1;
        
	return part;
}

/************************************************************
 *  Intel is changing the EFI Spec. (after v1.02) to say that a
 *  disk is considered to have a GPT label only if the GPT
 *  structures are correct, and the MBR is actually a Protective
 *  MBR (has one 0xEE type partition).
 *  Problem occurs when a GPT-partitioned disk is then
 *  edited with a legacy (non-GPT-aware) application, such as
 *  fdisk (which doesn't generally erase the PGPT or AGPT).
 *  How should such a disk get handled?  As a GPT disk (throwing
 *  away the fdisk changes), or as an MSDOS disk (throwing away
 *  the GPT information).  Previously, I've taken the GPT-is-right,
 *  MBR is wrong, approach, to stay consistent with the EFI Spec.
 *  Intel disagrees, saying the disk should then be treated
 *  as having a msdos label, not a GPT label.  If this is true,
 *  then what's the point of having an AGPT, since if the PGPT
 *  is screwed up, likely the PMBR is too, and the PMBR becomes
 *  a single point of failure.
 *  So, in the Linux kernel, I'm going to test for PMBR, and
 *  warn if it's not there, and treat the disk as MSDOS, with a note
 *  for users to use Parted to "fix up" their disk if they
 *  really want it to be considered GPT.
 ************************************************************/
static int
gpt_read (PedDisk * disk)
{
	GPTDiskData *gpt_disk_data = disk->disk_specific;
	GuidPartitionTableHeader_t* gpt;
	GuidPartitionEntry_t* ptes;
	int ptes_size;
	int i;
#ifndef DISCOVER_ONLY
	int write_back = 0;
#endif

	ped_disk_delete_all (disk);

        /* 
         * motivation: let the user decide about the pmbr... during
         * ped_disk_probe(), they probably didn't get a choice...
         */
	if (!gpt_probe (disk->dev))
		goto error;

	if (_read_header (disk->dev, &gpt, 1)) {
		PED_ASSERT ((PedSector) PED_LE64_TO_CPU (gpt->AlternateLBA)
				<= disk->dev->length - 1, goto error_free_gpt);
		if ((PedSector) PED_LE64_TO_CPU (gpt->AlternateLBA)
				< disk->dev->length - 1) {
			char* zeros = ped_malloc (pth_get_size (disk->dev));

#ifndef DISCOVER_ONLY
			if (ped_exception_throw (
				PED_EXCEPTION_ERROR,
				PED_EXCEPTION_FIX | PED_EXCEPTION_CANCEL,
		_("The backup GPT table is not at the end of the disk, as it "
		  "should be.  This might mean that another operating system "
		  "believes the disk is smaller.  Fix, by moving the backup "
		  "to the end (and removing the old backup)?"))
					== PED_EXCEPTION_CANCEL)
				goto error_free_gpt;

			write_back = 1;
			memset (zeros, 0, disk->dev->sector_size);
			ped_device_write (disk->dev, zeros,
					  PED_LE64_TO_CPU (gpt->AlternateLBA),
					  1);
#endif /* !DISCOVER_ONLY */
		}
	} else { /* primary GPT *not* ok */
		int alternate_ok = 0;

#ifndef DISCOVER_ONLY
		write_back = 1;
#endif

		if ((PedSector) PED_LE64_TO_CPU (gpt->AlternateLBA)
				< disk->dev->length - 1) {
			alternate_ok = _read_header (disk->dev, &gpt,
					    PED_LE64_TO_CPU(gpt->AlternateLBA));
		}
		if (!alternate_ok) {
			alternate_ok = _read_header (disk->dev, &gpt,
						     disk->dev->length - 1);
		}

		if (alternate_ok) {
			if (ped_exception_throw (
				PED_EXCEPTION_ERROR,
				PED_EXCEPTION_OK_CANCEL,
				_("The primary GPT table is corrupt, but the "
				  "backup appears OK, so that will be used."))
				    == PED_EXCEPTION_CANCEL)
				goto error_free_gpt;
		} else {
			ped_exception_throw (
				PED_EXCEPTION_ERROR,
				PED_EXCEPTION_CANCEL,
				_("Both the primary and backup GPT tables "
				  "are corrupt.  Try making a fresh table, "
				  "and using Parted's rescue feature to "
				  "recover partitions."));
			goto error;
		}
	}

	if (!_parse_header (disk, gpt))
		goto error_free_gpt;


	ptes_size = sizeof (GuidPartitionEntry_t) * gpt_disk_data->entry_count;
	ptes = (GuidPartitionEntry_t*) ped_malloc (ptes_size);
	if (!ped_device_read (disk->dev, ptes,
			      PED_LE64_TO_CPU(gpt->PartitionEntryLBA),
			      ptes_size / disk->dev->sector_size))
		goto error_free_ptes;

	for (i = 0; i < gpt_disk_data->entry_count; i++) {
		PedPartition* part;
		PedConstraint* constraint_exact;

		if (!guid_cmp (ptes[i].PartitionTypeGuid, UNUSED_ENTRY_GUID))
			continue;

		part = _parse_part_entry (disk, &ptes[i]);
		if (!part)
			goto error_delete_all;

		part->fs_type = ped_file_system_probe (&part->geom);
		part->num = i + 1;

		constraint_exact = ped_constraint_exact (&part->geom);
		if (!ped_disk_add_partition(disk, part, constraint_exact)) {
			ped_partition_destroy (part);
			goto error_delete_all;
		}
		ped_constraint_destroy (constraint_exact);
	}
	ped_free (ptes);

#ifndef DISCOVER_ONLY
	if (write_back)
		ped_disk_commit_to_dev (disk);
#endif

	return 1;

error_delete_all:
	ped_disk_delete_all (disk);
error_free_ptes:
	ped_free (ptes);
error_free_gpt:
        pth_free (gpt);
error:
	return 0;
}

#ifndef DISCOVER_ONLY
/* Writes the protective MBR (to keep DOS happy) */
static int
_write_pmbr (PedDevice * dev)
{
	LegacyMBR_t pmbr;

	memset(&pmbr, 0, sizeof(pmbr));
	pmbr.Signature = PED_CPU_TO_LE16(MSDOS_MBR_SIGNATURE);
	pmbr.PartitionRecord[0].OSType      = EFI_PMBR_OSTYPE_EFI;
	pmbr.PartitionRecord[0].StartSector = 1;
	pmbr.PartitionRecord[0].EndHead     = 0xFE;
	pmbr.PartitionRecord[0].EndSector   = 0xFF;
	pmbr.PartitionRecord[0].EndTrack    = 0xFF;
	pmbr.PartitionRecord[0].StartingLBA = PED_CPU_TO_LE32(1);
	if ((dev->length - 1ULL) > 0xFFFFFFFFULL) 
		pmbr.PartitionRecord[0].SizeInLBA = PED_CPU_TO_LE32(0xFFFFFFFF);
	else
		pmbr.PartitionRecord[0].SizeInLBA = PED_CPU_TO_LE32(dev->length - 1UL);

	return ped_device_write (dev, &pmbr, GPT_PMBR_LBA, GPT_PMBR_SECTORS);
}

static void
_generate_header (PedDisk* disk, int alternate, uint32_t ptes_crc,
		  GuidPartitionTableHeader_t** gpt_p)
{
	GPTDiskData* gpt_disk_data = disk->disk_specific;
        GuidPartitionTableHeader_t* gpt;

        *gpt_p = pth_new_zeroed (disk->dev);
        
        gpt = *gpt_p;
        
	gpt->Signature = PED_CPU_TO_LE64 (GPT_HEADER_SIGNATURE);
	gpt->Revision = PED_CPU_TO_LE32 (GPT_HEADER_REVISION_V1_00);
	gpt->HeaderSize = PED_CPU_TO_LE32 (pth_get_size_static (disk->dev)); /* per 1.00 spec */
	gpt->HeaderCRC32 = 0;
	gpt->Reserved1 = 0;

	if (alternate) {
		PedSector ptes_size = gpt_disk_data->entry_count
				      * sizeof (GuidPartitionEntry_t) / disk->dev->sector_size;

		gpt->MyLBA = PED_CPU_TO_LE64 (disk->dev->length - 1);
		gpt->AlternateLBA = PED_CPU_TO_LE64 (1);
		gpt->PartitionEntryLBA
			= PED_CPU_TO_LE64 (disk->dev->length - 2 - ptes_size);
	} else {
		gpt->MyLBA = PED_CPU_TO_LE64 (1);
		gpt->AlternateLBA = PED_CPU_TO_LE64 (disk->dev->length - 1);
		gpt->PartitionEntryLBA = PED_CPU_TO_LE64 (2);
	}

	gpt->FirstUsableLBA = PED_CPU_TO_LE64 (gpt_disk_data->data_area.start);
	gpt->LastUsableLBA = PED_CPU_TO_LE64 (gpt_disk_data->data_area.end);
	gpt->DiskGUID = gpt_disk_data->uuid;
	gpt->NumberOfPartitionEntries
		= PED_CPU_TO_LE32 (gpt_disk_data->entry_count);
	gpt->SizeOfPartitionEntry
		= PED_CPU_TO_LE32 (sizeof (GuidPartitionEntry_t));
	gpt->PartitionEntryArrayCRC32 = PED_CPU_TO_LE32 (ptes_crc);
	gpt->HeaderCRC32 = PED_CPU_TO_LE32 (pth_crc32 (disk->dev, gpt));
}

static void
_partition_generate_part_entry (PedPartition* part, GuidPartitionEntry_t* pte)
{
	GPTPartitionData* gpt_part_data = part->disk_specific;
	unsigned int i;

	PED_ASSERT (gpt_part_data != NULL, return);

	pte->PartitionTypeGuid = gpt_part_data->type;
	pte->UniquePartitionGuid = gpt_part_data->uuid;
	pte->StartingLBA = PED_CPU_TO_LE64(part->geom.start);
	pte->EndingLBA = PED_CPU_TO_LE64(part->geom.end);
	memset (&pte->Attributes, 0, sizeof (GuidPartitionEntryAttributes_t));

        if (gpt_part_data->hidden)
                pte->Attributes.RequiredToFunction = 1;
        
	for (i = 0; i < 72 / sizeof(efi_char16_t); i++)
		pte->PartitionName[i]
			= (efi_char16_t) PED_CPU_TO_LE16(
				(uint16_t) gpt_part_data->name[i]);
}

static int
gpt_write(PedDisk * disk)
{
	GPTDiskData* gpt_disk_data;
	GuidPartitionEntry_t* ptes;
	uint32_t ptes_crc;
        uint8_t* pth_raw = ped_malloc (pth_get_size (disk->dev));
	GuidPartitionTableHeader_t* gpt;
	PedPartition* part;
	int ptes_size;
	unsigned int i;

	PED_ASSERT (disk != NULL, goto error);
	PED_ASSERT (disk->dev != NULL, goto error);
	PED_ASSERT (disk->disk_specific != NULL, goto error);

	gpt_disk_data = disk->disk_specific;

	ptes_size = sizeof (GuidPartitionEntry_t) * gpt_disk_data->entry_count;
	ptes = (GuidPartitionEntry_t*) ped_malloc (ptes_size);
	if (!ptes)
		goto error;
	memset (ptes, 0, ptes_size);
	for (part = ped_disk_next_partition (disk, NULL); part;
	     part = ped_disk_next_partition (disk, part)) {
		if (part->type != 0)
			continue;
		_partition_generate_part_entry (part, &ptes[part->num - 1]);
	}

	ptes_crc = efi_crc32 (ptes, ptes_size);

	/* Write protective MBR */
	if (!_write_pmbr (disk->dev))
		goto error_free_ptes;

	/* Write PTH and PTEs */
	_generate_header (disk, 0, ptes_crc, &gpt);
        pth_raw = pth_get_raw (disk->dev, gpt);
	if (!ped_device_write (disk->dev, pth_raw, 1, 1))
		goto error_free_ptes;
	if (!ped_device_write (disk->dev, ptes, 2, ptes_size / disk->dev->sector_size))
		goto error_free_ptes;

	/* Write Alternate PTH & PTEs */
	_generate_header (disk, 1, ptes_crc, &gpt);
        pth_raw = pth_get_raw (disk->dev, gpt);
	if (!ped_device_write (disk->dev, pth_raw, disk->dev->length - 1, 1))
		goto error_free_ptes;
	if (!ped_device_write (disk->dev, ptes,
			       disk->dev->length - 2 - ptes_size / disk->dev->sector_size,
			       ptes_size / disk->dev->sector_size))
		goto error_free_ptes;

	ped_free (ptes);
	return ped_device_sync (disk->dev);

error_free_ptes:
	ped_free (ptes);
error:
	return 0;
}
#endif /* !DISCOVER_ONLY */

static int
add_metadata_part(PedDisk * disk, PedSector start, PedSector length)
{
	PedPartition* part;
	PedConstraint* constraint_exact;
	PED_ASSERT(disk != NULL, return 0);

	part = ped_partition_new (disk, PED_PARTITION_METADATA, NULL,
				  start, start + length - 1);
	if (!part)
		goto error;

	constraint_exact = ped_constraint_exact (&part->geom);
	if (!ped_disk_add_partition (disk, part, constraint_exact))
		goto error_destroy_constraint;
	ped_constraint_destroy (constraint_exact);
	return 1;

error_destroy_constraint:
	ped_constraint_destroy (constraint_exact);
error_destroy_part:
	ped_partition_destroy (part);
error:
	return 0;
}

static PedPartition*
gpt_partition_new (const PedDisk* disk,
		  PedPartitionType part_type, const PedFileSystemType* fs_type,
		  PedSector start, PedSector end)
{
	PedPartition* part;
	GPTPartitionData* gpt_part_data;

	part = _ped_partition_alloc (disk, part_type, fs_type, start, end);
	if (!part)
		goto error;

	if (part_type != 0)
		return part;

	gpt_part_data = part->disk_specific =
		ped_malloc (sizeof (GPTPartitionData));
	if (!gpt_part_data)
		goto error_free_part;

	gpt_part_data->type = PARTITION_BASIC_DATA_GUID;
	gpt_part_data->lvm = 0;
	gpt_part_data->raid = 0;
	gpt_part_data->boot = 0;
	gpt_part_data->hp_service = 0;
        gpt_part_data->hidden = 0;
        gpt_part_data->msftres = 0;
	uuid_generate ((unsigned char*) &gpt_part_data->uuid);
	swap_uuid_and_efi_guid((unsigned char*)(&gpt_part_data->uuid));
	strcpy (gpt_part_data->name, "");
	return part;

error_free_part:
	_ped_partition_free (part);
error:
	return NULL;
}

static PedPartition*
gpt_partition_duplicate (const PedPartition* part)
{
	PedPartition* result;
	GPTPartitionData* part_data = part->disk_specific;
	GPTPartitionData* result_data;

	result = _ped_partition_alloc (part->disk, part->type, part->fs_type,
				       part->geom.start, part->geom.end);
	if (!result)
		goto error;
	result->num = part->num;

	if (result->type != 0)
		return result;

	result_data = result->disk_specific =
		ped_malloc (sizeof (GPTPartitionData));
	if (!result_data)
		goto error_free_part;

	result_data->type = part_data->type;
	result_data->uuid = part_data->uuid;
	strcpy (result_data->name, part_data->name);
	return result;

error_free_part:
	_ped_partition_free (result);
error:
	return NULL;
}

static void
gpt_partition_destroy (PedPartition *part)
{
	if (part->type == 0) {
		PED_ASSERT (part->disk_specific != NULL, return);
		ped_free (part->disk_specific);
	}

	_ped_partition_free (part);
}

static int
gpt_partition_set_system (PedPartition* part, const PedFileSystemType* fs_type)
{
	GPTPartitionData* gpt_part_data = part->disk_specific;

	PED_ASSERT (gpt_part_data != NULL, return 0);

	part->fs_type = fs_type;

	if (gpt_part_data->lvm) {
		gpt_part_data->type = PARTITION_LVM_GUID;
		return 1;
	}
	if (gpt_part_data->raid) {
		gpt_part_data->type = PARTITION_RAID_GUID;
		return 1;
	}
	if (gpt_part_data->boot) {
		gpt_part_data->type = PARTITION_SYSTEM_GUID;
		return 1;
	}
	if (gpt_part_data->hp_service) {
		gpt_part_data->type = PARTITION_HPSERVICE_GUID;
		return 1;
	}
        if (gpt_part_data->msftres) {
                gpt_part_data->type = PARTITION_MSFT_RESERVED_GUID;
                return 1;
        }
        
	if (fs_type) {
		if (strncmp (fs_type->name, "fat", 3) == 0
		    || strcmp (fs_type->name, "ntfs") == 0) {
			gpt_part_data->type = PARTITION_MSFT_RESERVED_GUID;
			return 1;
		}
		if (strncmp (fs_type->name, "hfs", 3) == 0) {
			gpt_part_data->type = PARTITION_APPLE_HFS_GUID;
			return 1;
		}
		if (strstr (fs_type->name, "swap")) {
			gpt_part_data->type = PARTITION_SWAP_GUID;
			return 1;
		}
	}

	gpt_part_data->type = PARTITION_BASIC_DATA_GUID;
	return 1;
}

/* Allocate metadata partitions for the GPTH and PTES */
static int
gpt_alloc_metadata (PedDisk * disk)
{
	PedSector gptlength, pteslength = 0;
	GPTDiskData *gpt_disk_data;

	PED_ASSERT(disk != NULL, return 0);
	PED_ASSERT(disk->dev != NULL, return 0);
	PED_ASSERT(disk->disk_specific != NULL, return 0);
	gpt_disk_data = disk->disk_specific;

	gptlength = ped_div_round_up (sizeof (GuidPartitionTableHeader_t), disk->dev->sector_size);
	pteslength = ped_div_round_up (gpt_disk_data->entry_count
				       * sizeof (GuidPartitionEntry_t), disk->dev->sector_size);

	/* metadata at the start of the disk includes the MBR */
	if (!add_metadata_part(disk, GPT_PMBR_LBA,
			       GPT_PMBR_SECTORS + gptlength + pteslength))
		return 0;

	/* metadata at the end of the disk */
	if (!add_metadata_part(disk, disk->dev->length - gptlength - pteslength,
			       gptlength + pteslength))
		return 0;

	return 1;
}

/* Does nothing, as the read/new/destroy functions maintain part->num */
static int
gpt_partition_enumerate (PedPartition* part)
{
	GPTDiskData* gpt_disk_data = part->disk->disk_specific;
	int i;

	/* never change the partition numbers */
	if (part->num != -1)
		return 1;

	for (i = 1; i <= gpt_disk_data->entry_count; i++) {
		if (!ped_disk_get_partition (part->disk, i)) {
			part->num = i;
			return 1;
		}
	}

	PED_ASSERT (0, return 0); return 0;
}

static int
gpt_partition_set_flag(PedPartition *part,
		       PedPartitionFlag flag,
		       int state)
{
	GPTPartitionData *gpt_part_data;
	PED_ASSERT(part != NULL, return 0);
	PED_ASSERT(part->disk_specific != NULL, return 0);
	gpt_part_data = part->disk_specific;

	switch (flag) {
	case PED_PARTITION_BOOT:
		gpt_part_data->boot = state;
		if (state)
                        gpt_part_data->raid 
                                = gpt_part_data->lvm
                                = gpt_part_data->hp_service
                                = gpt_part_data->msftres = 0;
		return gpt_partition_set_system (part, part->fs_type);
	case PED_PARTITION_RAID:
		gpt_part_data->raid = state;
		if (state)
                        gpt_part_data->boot
                                = gpt_part_data->lvm
                                = gpt_part_data->hp_service
                                = gpt_part_data->msftres = 0;
		return gpt_partition_set_system (part, part->fs_type);
	case PED_PARTITION_LVM:
		gpt_part_data->lvm = state;
		if (state)
                        gpt_part_data->boot
                                = gpt_part_data->raid
                                = gpt_part_data->hp_service
                                = gpt_part_data->msftres = 0;
		return gpt_partition_set_system (part, part->fs_type);
	case PED_PARTITION_HPSERVICE:
		gpt_part_data->hp_service = state;
		if (state)
                        gpt_part_data->boot
                                = gpt_part_data->raid
                                = gpt_part_data->lvm
                                = gpt_part_data->msftres = 0;
		return gpt_partition_set_system (part, part->fs_type);
        case PED_PARTITION_MSFT_RESERVED:
                gpt_part_data->msftres = state;
                if (state)
                        gpt_part_data->boot
                                = gpt_part_data->raid
                                = gpt_part_data->lvm
                                = gpt_part_data->hp_service = 0;
                return gpt_partition_set_system (part, part->fs_type);
        case PED_PARTITION_HIDDEN:
                gpt_part_data->hidden = state;
                return 1;
	case PED_PARTITION_SWAP:
	case PED_PARTITION_ROOT:
	case PED_PARTITION_LBA:
	default:
		return 0;
	}
	return 1;
}

static int
gpt_partition_get_flag(const PedPartition *part, PedPartitionFlag flag)
{
	GPTPartitionData *gpt_part_data;
	PED_ASSERT(part->disk_specific != NULL, return 0);
	gpt_part_data = part->disk_specific;

	switch (flag) {
	case PED_PARTITION_RAID:
		return gpt_part_data->raid;
	case PED_PARTITION_LVM:
		return gpt_part_data->lvm;
	case PED_PARTITION_BOOT:
		return gpt_part_data->boot;
	case PED_PARTITION_HPSERVICE:
		return gpt_part_data->hp_service;
        case PED_PARTITION_MSFT_RESERVED:
                return gpt_part_data->msftres;
        case PED_PARTITION_HIDDEN:
                       return gpt_part_data->hidden;
	case PED_PARTITION_SWAP:
	case PED_PARTITION_LBA:
	case PED_PARTITION_ROOT:
	default:
		return 0;
	}
	return 0;
}

static int
gpt_partition_is_flag_available(const PedPartition * part,
				PedPartitionFlag flag)
{
	switch (flag) {
	case PED_PARTITION_RAID:
	case PED_PARTITION_LVM:
	case PED_PARTITION_BOOT:
	case PED_PARTITION_HPSERVICE:
        case PED_PARTITION_MSFT_RESERVED:
        case PED_PARTITION_HIDDEN:        
		return 1;
	case PED_PARTITION_SWAP:
	case PED_PARTITION_ROOT:
	case PED_PARTITION_LBA:
	default:
		return 0;
	}
	return 0;
}

static void
gpt_partition_set_name (PedPartition *part, const char *name)
{
	GPTPartitionData *gpt_part_data = part->disk_specific;

	strncpy (gpt_part_data->name, name, 36);
	gpt_part_data->name [36] = 0;
}

static const char *
gpt_partition_get_name (const PedPartition * part)
{
	GPTPartitionData* gpt_part_data = part->disk_specific;
	return gpt_part_data->name;
}

static int
gpt_get_max_primary_partition_count (const PedDisk *disk)
{
	const GPTDiskData* gpt_disk_data = disk->disk_specific;
	return gpt_disk_data->entry_count;
}

static PedConstraint*
_non_metadata_constraint (const PedDisk* disk)
{
	GPTDiskData* gpt_disk_data = disk->disk_specific;

	return ped_constraint_new_from_max (&gpt_disk_data->data_area);
}

static int
gpt_partition_align (PedPartition* part, const PedConstraint* constraint)
{
	PED_ASSERT (part != NULL, return 0);

	if (_ped_partition_attempt_align (part, constraint,
			_non_metadata_constraint (part->disk)))
	       	return 1;

#ifndef DISCOVER_ONLY
	ped_exception_throw (
		PED_EXCEPTION_ERROR,
		PED_EXCEPTION_CANCEL,
		_("Unable to satisfy all constraints on the partition."));
#endif
	return 0;
}

static PedDiskOps gpt_disk_ops = {
	probe:		gpt_probe,
#ifndef DISCOVER_ONLY
	clobber:	gpt_clobber,
#else
	clobber:	NULL,
#endif
	alloc:		gpt_alloc,
	duplicate:	gpt_duplicate,
	free:		gpt_free,
	read:		gpt_read,
#ifndef DISCOVER_ONLY
	write:		gpt_write,
#else
	write:		NULL,
#endif
	partition_new:			gpt_partition_new,
	partition_duplicate:		gpt_partition_duplicate,
	partition_destroy:		gpt_partition_destroy,
	partition_set_system:		gpt_partition_set_system,
	partition_set_flag:		gpt_partition_set_flag,
	partition_get_flag:		gpt_partition_get_flag,
	partition_is_flag_available:	gpt_partition_is_flag_available,
	partition_set_name:		gpt_partition_set_name,
	partition_get_name:		gpt_partition_get_name,
	partition_align:		gpt_partition_align,
	partition_enumerate:		gpt_partition_enumerate,
	alloc_metadata:			gpt_alloc_metadata,
	get_max_primary_partition_count: gpt_get_max_primary_partition_count
};

static PedDiskType gpt_disk_type = {
	next:		NULL,
	name:		"gpt",
	ops:		&gpt_disk_ops,
	features:	PED_DISK_TYPE_PARTITION_NAME
};

void
ped_disk_gpt_init()
{
	PED_ASSERT(sizeof(GuidPartitionEntryAttributes_t) == 8, return);
	PED_ASSERT(sizeof(GuidPartitionEntry_t) == 128, return);

	ped_register_disk_type(&gpt_disk_type);
}

void
ped_disk_gpt_done()
{
	ped_unregister_disk_type(&gpt_disk_type);
}

